package com.gitee.huanminabc.jfastcache;

import lombok.extern.slf4j.Slf4j;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.mapdb.HTreeMap;
import org.mapdb.Serializer;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 通过内存映射技术可以做到比直接操作文件快几倍的速度, 从目前体验来说
 * https://jankotek.gitbooks.io/mapdb/content/performance/  优化
 */
@Slf4j
public class MapDBUtil {
    // 常量定义
    private static final String tempName = "java_local_cache";
    private static final int CLEANUP_DAYS = 7; // 清理超过7天的缓存文件
    private static final int INITIAL_SIZE_MB = 10; // 初始大小10MB
    private static final int MAX_RETRIES = 5; // 最大重试次数
    private static final int RETRY_DELAY_MS = 200; // 重试等待时间（毫秒）
    private static final long MAX_CACHE_SIZE_GB = 4; // 最大缓存大小4GB
    
    private static final File stopFile = new File(getTempPath() + File.separator + "cache");
    
    //本地缓存文件地址
    private static String getTempPath() {
        return System.getProperty("java.io.tmpdir") + File.separator + tempName;
    }
    static {
        String tempPath = getTempPath();
        //缓存目录不存在则创建
        File tempDir = new File(tempPath);
        if (!tempDir.exists()) {
            tempDir.mkdirs();
        }

        //读取目录下所有缓存文件
        File[] files = tempDir.listFiles();
        if (files != null) {
            //删除最近7天之前没有修改的的缓存文件,避免缓存文件过多占用磁盘空间
            long sevenDaysAgo = System.currentTimeMillis() - (1000L * 60 * 60 * 24 * CLEANUP_DAYS);
            List<File> collect = Arrays.stream(files)
                    .filter(file -> file.lastModified() < sevenDaysAgo)
                    .collect(Collectors.toList());
            collect.forEach(File::delete);
        }
    }
    //每次创建一个新的数据库,key提升性能,如果都在一个数据库中,那么会导致数据库文件过大,性能下降
    public static DB defaultCreate(String key) {
        String filePath= getTempPath() + File.separator + key;
        log.info(key+"本地缓存文件地址:{}",filePath);
        File dbFile = new File(filePath);
        
        DBMaker.Maker maker = DBMaker
                .fileDB(filePath)
                .fileChannelEnable()//使用 FileChannel 代替 RandomAccessFile 可能会提高性能
                .fileMmapEnable() //使用内存映射文件, 会提高性能
                .fileMmapEnableIfSupported()
                .fileMmapPreclearDisable()
                .allocateStartSize(1024L * 1024 * INITIAL_SIZE_MB)  // 初始大小10mb
                //同步、刷新和关闭等操作必须遍历所有缓冲区。因此，增量大小越大可以加快提交和关闭操作的速度。
                .allocateIncrement(1024L * 1024 * INITIAL_SIZE_MB)
                .cleanerHackEnable() //使用额外的线程清理内存映射, 但是会增加性能开销
                .closeOnJvmShutdown(); //关闭JVM时自动关闭数据库, 所以就算忘记关闭数据库也不会有问题

        DB make = null;
        int retryCount = 0;
        
        while (retryCount < MAX_RETRIES) {
            try {
                make = maker.make();
                break; // 成功创建，退出循环
            } catch (Exception e) {
                retryCount++;
                String errorMsg = e.getMessage() != null ? e.getMessage() : "";
                log.warn("创建数据库失败，尝试次数: {}/{}, 错误: {}", retryCount, MAX_RETRIES, errorMsg);
                
                // 如果是文件锁定错误，等待一段时间让之前的进程释放锁
                boolean isFileLocked = errorMsg.contains("locked") || errorMsg.contains("FileLocked") || 
                                      e.getClass().getSimpleName().contains("FileLocked");
                if (isFileLocked) {
                    try {
                        // 等待一小段时间，让之前的进程释放锁
                        Thread.sleep(RETRY_DELAY_MS * retryCount);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        log.warn("等待文件锁释放时被中断", ie);
                    }
                }
                
                // 尝试删除锁定的文件
                deleteLockedFiles(dbFile, stopFile, filePath, isFileLocked);
                
                // 如果已经重试了最大次数，抛出异常
                if (retryCount >= MAX_RETRIES) {
                    throw new RuntimeException("创建数据库失败，已重试" + MAX_RETRIES + "次: " + errorMsg, e);
                }
            }
        }
        
        // 确保数据库创建成功
        if (make == null) {
            throw new RuntimeException("创建数据库失败，无法创建数据库实例");
        }
        
        //文件内容加载到磁盘缓存中, 这样key减少io操作
        make.getStore().fileLoad();
        return make;
    }

    /**
     * 删除锁定的文件
     * 
     * @param dbFile 数据库文件
     * @param stopFile 停止文件（如果存在）
     * @param filePath 文件路径（用于日志）
     * @param isFileLocked 是否是文件锁定错误
     */
    private static void deleteLockedFiles(File dbFile, File stopFile, String filePath, boolean isFileLocked) {
        try {
            // 尝试删除数据库文件
            if (dbFile.exists()) {
                boolean deleted = dbFile.delete();
                if (isFileLocked) {
                    log.info("尝试删除锁定的文件: {}, 结果: {}", filePath, deleted);
                } else {
                    log.debug("尝试删除文件: {}, 结果: {}", filePath, deleted);
                }
            }
            
            // 尝试删除stopFile
            if (stopFile.exists()) {
                stopFile.delete();
            }
        } catch (Exception ex) {
            log.warn("删除文件时出错", ex);
        }
    }


    //创建HTreeMap
    @SuppressWarnings("unchecked")
    public static <K, V> org.mapdb.HTreeMap<K, V> createHTreeMap(DB db, String name) {
        HTreeMap<K, V> orOpen = (HTreeMap<K, V>) db.hashMap(name).
                keySerializer(Serializer.STRING).
                valueSerializer(Serializer.JAVA).
                expireStoreSize(1024L * 1024 * 1024 * MAX_CACHE_SIZE_GB). //最大缓存4G,超过了就会删除最早访问的
                expireAfterGet().
                createOrOpen();
        return orOpen;
    }


}
